package com.tttshaoqi.server.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.WeakKeyException;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

public class JwtUtil {

    // HMAC-SHA256 算法要求的最小密钥长度（256位 = 32字节）
    private static final int MIN_HS256_KEY_SIZE = 32;
    private final SecretKey secretKey;
    private final long ttlMillis;

    /**
     * 构造方法（添加参数校验，避免弱密钥和无效参数）
     * @param secret 密钥字符串（需满足：转换为 UTF-8 字节数组后长度 ≥ 32 字节）
     * @param ttlSeconds Token 有效期（秒），建议 ≥ 300 秒（5分钟），避免过短或过长
     * @throws IllegalArgumentException 若参数无效（如 secret 为空、ttlSeconds ≤ 0）
     * @throws WeakKeyException 若密钥长度不足 256 位
     * @throws InvalidKeyException 若密钥格式无效
     */
    public JwtUtil(String secret, long ttlSeconds) {
        // 1. 校验入参非空
        if (Objects.isNull(secret) || secret.trim().isEmpty()) {
            throw new IllegalArgumentException("JWT 密钥不能为空！");
        }
        if (ttlSeconds <= 0) {
            throw new IllegalArgumentException("Token 有效期必须大于 0 秒！建议设置为 300~3600 秒");
        }

        // 2. 转换密钥为字节数组并校验长度
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        if (keyBytes.length < MIN_HS256_KEY_SIZE) {
            throw new WeakKeyException(
                    String.format("JWT 密钥长度不足！当前密钥长度：%d 字节，HS256 算法要求至少 %d 字节（256位）",
                            keyBytes.length, MIN_HS256_KEY_SIZE)
            );
        }

        // 3. 生成标准 HMAC 密钥
        this.secretKey = Keys.hmacShaKeyFor(keyBytes);
        this.ttlMillis = ttlSeconds * 1000L;
    }

    /**
     * 生成 JWT Token
     * @param subject Token 主题（通常为用户唯一标识，如用户 ID）
     * @param claims 额外业务参数（如：role=ADMIN, permission=USER_READ）
     * @return 紧凑格式的 JWT Token（Header.Payload.Signature）
     * @throws IllegalArgumentException 若 subject 为空
     */
    public String generateToken(String subject, Map<String, Object> claims) {
        // 校验主题非空（避免生成无意义的 Token）
        if (Objects.isNull(subject) || subject.trim().isEmpty()) {
            throw new IllegalArgumentException("Token 主题（subject）不能为空！建议传入用户 ID 等唯一标识");
        }

        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        Date expiration = new Date(nowMillis + ttlMillis);

        // 构建 Token（链式调用，清晰简洁）
        return Jwts.builder()
                .setClaims(Objects.nonNull(claims) ? claims : Map.of()) // 若 claims 为 null，使用空 Map
                .setSubject(subject)
                .setIssuedAt(now) // 签发时间
                .setExpiration(expiration) // 过期时间
                .signWith(secretKey, SignatureAlgorithm.HS256) // 明确指定 HS256 算法
                .compact();
    }

    /**
     * 解析 JWT Token，获取 Claims（包含主题、过期时间、自定义参数）
     * @param token 待解析的 JWT Token
     * @return 解析后的 Claims 对象
     * @throws ExpiredJwtException Token 已过期
     * @throws MalformedJwtException Token 格式错误（如缺少 "."、字段非法）
     * @throws SignatureException Token 签名无效（如密钥不匹配、Token 被篡改）
     * @throws IllegalArgumentException Token 为空或空白字符串
     */
    public Claims parseToken(String token) {
        // 校验 Token 非空
        if (Objects.isNull(token) || token.trim().isEmpty()) {
            throw new IllegalArgumentException("待解析的 JWT Token 不能为空！");
        }

        try {
            // 解析 Token（仅信任自己签发的签名，避免伪造 Token）
            return Jwts.parserBuilder()
                    .setSigningKey(secretKey) // 必须与生成 Token 时的密钥一致
                    .build()
                    .parseClaimsJws(token) // 解析为 JWS（带签名的 JWT）
                    .getBody(); // 获取 Payload 中的 Claims
        } catch (ExpiredJwtException e) {
            // Token 过期：可自定义异常信息，便于业务层区分处理
            throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "JWT Token 已过期，请重新登录", e);
        } catch (MalformedJwtException e) {
            // Token 格式错误：如用户手动修改 Token 字符串
            throw new MalformedJwtException("JWT Token 格式错误，可能被篡改", e);
        } catch (SignatureException e) {
            // 签名无效：如密钥不匹配、Token 被篡改
            throw new SignatureException("JWT Token 签名无效，可能被非法修改", e);
        }
    }

    // ------------------- 可选：添加工具方法，方便业务层使用 -------------------
    /**
     * 从 Token 中获取主题（用户唯一标识）
     */
    public String getSubjectFromToken(String token) {
        return parseToken(token).getSubject();
    }

    /**
     * 判断 Token 是否已过期
     */
    public boolean isTokenExpired(String token) {
        Claims claims = parseToken(token);
        Date expiration = claims.getExpiration();
        return expiration.before(new Date()); // 过期时间早于当前时间 → 已过期
    }

    /**
     * 从 Token 中获取自定义 Claim（如角色、权限）
     */
    public <T> T getClaimFromToken(String token, String claimName, Class<T> claimType) {
        Claims claims = parseToken(token);
        return claims.get(claimName, claimType);
    }
}